/* This file is part of Green.
*
* Copyright (C) 2005 The Research Foundation of State University of New York
* All Rights Under Copyright Reserved, The Research Foundation of S.U.N.Y.
*
* Green is free software, licensed under the terms of the Eclipse
* Public License, version 1.0. The license is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package edu.buffalo.cse.green.editor.model;
import static edu.buffalo.cse.green.constants.XMLConstants.XML_BENDPOINT;
import static edu.buffalo.cse.green.constants.XMLConstants.XML_BENDPOINTS;
import static edu.buffalo.cse.green.constants.XMLConstants.XML_BENDPOINT_X;
import static edu.buffalo.cse.green.constants.XMLConstants.XML_BENDPOINT_Y;
import static edu.buffalo.cse.green.constants.XMLConstants.XML_RELATIONSHIP;
import static edu.buffalo.cse.green.constants.XMLConstants.XML_RELATIONSHIP_CLASS;
import static edu.buffalo.cse.green.constants.XMLConstants.XML_RELATIONSHIP_SOURCE_TYPE;
import static edu.buffalo.cse.green.constants.XMLConstants.XML_RELATIONSHIP_TARGET_TYPE;
import static edu.buffalo.cse.green.editor.controller.PropertyChange.RelationshipCardinality;
import static edu.buffalo.cse.green.editor.controller.PropertyChange.RelationshipSource;
import static edu.buffalo.cse.green.editor.controller.PropertyChange.RelationshipTarget;
import static edu.buffalo.cse.green.editor.model.RelationshipKind.Cumulative;
import static edu.buffalo.cse.green.editor.model.RelationshipKind.Single;
import static org.eclipse.jdt.core.dom.ASTNode.METHOD_DECLARATION;
import static org.eclipse.jdt.core.dom.ASTNode.INITIALIZER;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.commands.Command;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Initializer;
import edu.buffalo.cse.green.GreenException;
import edu.buffalo.cse.green.PlugIn;
import edu.buffalo.cse.green.editor.DiagramEditor;
import edu.buffalo.cse.green.editor.action.ContextAction;
import edu.buffalo.cse.green.editor.controller.AbstractPart;
import edu.buffalo.cse.green.editor.model.commands.DeleteCommand;
import edu.buffalo.cse.green.editor.model.commands.HideRelationshipCommand;
import edu.buffalo.cse.green.editor.view.GreenBendpoint;
import edu.buffalo.cse.green.relationships.Relationship;
import edu.buffalo.cse.green.relationships.RelationshipGroup;
import edu.buffalo.cse.green.relationships.RelationshipRemover;
import edu.buffalo.cse.green.xml.XMLConverter;
/**
* Models a relationship. Holds a source, target, and list of bendpoints.
* Relationships can be shown implicitly or explicitly (default). When a
* relationship is implicit, if the source or target is invisible, the border
* color changes
* @author bcmartin
*/
public class RelationshipModel extends AbstractModel<AbstractModel, RootModel, IJavaElement> implements Cloneable {
/**
* The source and target types (in the diagram) that this relationship
* is between
*/
private IType _sourceType, _targetType;
/**
* @return A representation of the cardinality.
*
* @throws JavaModelException
*
* @author Gene Wang
*/
@SuppressWarnings("boxing")
public String getCardinality() throws JavaModelException {
final String NPE = "Node is not a child of a MethodDeclaration or Initializer block.";
final int INF = 999999;
boolean isGeneric = false;
int min = INF;
int max = 0;
int constructors = 0;
int uConstructors = 0;
Map<ASTNode, Integer> cardinality = new HashMap<ASTNode, Integer>();
RelationshipKind flags =
PlugIn.getRelationshipGroup(getPartClass()).getFlags();
if (flags.equals(Single)) {
return "1";
}
if (flags.equals(Cumulative)) {
for (Relationship relationship : _relationships) {
ASTNode locator = relationship.getFeatures().get(0);
while ((!(locator instanceof MethodDeclaration) &&
!(locator instanceof Initializer)) && locator != null) {
locator = locator.getParent();
}
if(locator == null) {
//If locator is not within an Initializer block or Method Declaration, it
//will be null. This is highly unlikely, but I can't guarantee it won't
//ever happen, so throw an NPE here just in case
throw new NullPointerException(NPE);
}
if(locator.getNodeType() == METHOD_DECLARATION) {
isGeneric = !((MethodDeclaration) locator).isConstructor();
}
else if (locator.getNodeType() == INITIALIZER) {
//This might be an erroneous assumption that Initializers
//cannot have higher cardinality
return "1";
}
}
}
for (IMethod method : _sourceType.getMethods()) {
if (method.isConstructor()) {
constructors++;
}
}
for (Relationship relationship : _relationships) {
ASTNode locator = relationship.getFeatures().get(0);
int card;
while ((!(locator instanceof MethodDeclaration) &&
!(locator instanceof Initializer)) && locator != null) {
locator = locator.getParent();
}
if(locator == null) {
throw new NullPointerException(NPE);
}
if (cardinality.get(locator) == null) {
card = 0;
uConstructors++;
} else {
card = cardinality.get(locator);
}
if((locator.getNodeType() == METHOD_DECLARATION) &&
((MethodDeclaration)locator).isConstructor()) {
if (relationship.isGeneric()) {
isGeneric = true;
cardinality.put(locator, card + relationship.getFeatures().size() - 2);
} else {
cardinality.put(locator, card + 1);
}
}
}
for (Integer card : cardinality.values()) {
if (card < min) {
min = card;
}
if (card > max) {
max = card;
}
}
if (uConstructors < constructors) {
min = 0;
}
if (min == INF) {
min = 0;
}
if (isGeneric) {
return min + "..*";
} else if (min == max) {
return "" + min;
} else {
return min + ".." + max;
}
}
/**
* A list of bendpoints belonging to the relationship. This should be
* updated whenever a bendpoint is added or removed
*/
private List<GreenBendpoint> _bendpoints =
new ArrayList<GreenBendpoint>();
/**
* The class that represents the controller part for this particular kind of
* relationship
*/
private Class _partClass = null;
/**
* A list of all relationships that have this particular source, target, and
* kind
*/
private Set<Relationship> _relationships;
/**
* Error message indicating the desired relationship is invalid.
*/
private String REL_NOT_SUPPORTED =
"The desired relationship is not supported";
public RelationshipModel() {
_relationships = new HashSet<Relationship>();
}
public RelationshipModel(IType sourceType, IType targetType,
Class partClass) {
this();
_partClass = partClass;
_sourceType = sourceType;
_targetType = targetType;
}
/**
* Adds a relationship to this model.
*
* @param relationship - The relationship.
* @return True if the relationship was added, false otherwise.
*/
public boolean addRelationship(Relationship relationship) {
return _relationships.add(relationship);
}
/**
* @return The <code>IType</code> representing the source of this
* relationship.
*/
public IType getSourceType() {
return _sourceType;
}
/**
* @return The <code>IType</code> representing the target of this
* relationship.
*/
public IType getTargetType() {
return _targetType;
}
/**
* @see edu.buffalo.cse.green.editor.model.AbstractModel#getPartClass()
*/
public Class getPartClass() {
if (_partClass == null) {
GreenException.illegalOperation("Part class is null");
}
return _partClass;
}
/**
* Sets the <code>Class</code> representing this kind of relationship.
*
* @param partClass - The class.
*/
public void setPartClass(Class partClass) {
_partClass = partClass;
}
/**
* Returns the source model.
*/
public TypeModel getSourceModel() {
if (getSourceType() == null) {
return null;
}
return getRootModel().getModelFromType(_sourceType);
}
/**
* Returns the target model.
*/
public TypeModel getTargetModel() {
if (getTargetType() == null) {
return null;
}
return getRootModel().getModelFromType(_targetType);
}
/**
* Sets a new source model.
*/
public void setSourceModel(TypeModel newSource) {
// update the source value
_sourceType = newSource.getType();
firePropertyChange(RelationshipSource, null, newSource);
}
/**
* Sets a new target model.
*/
public void setTargetModel(TypeModel newTarget) {
// update the target value
_targetType = newTarget.getType();
firePropertyChange(RelationshipTarget, null, newTarget);
}
/**
* Sets a new list of bendpoints.
*
* @param list - The list.
*/
public void setBendpointList(List<GreenBendpoint> list) {
_bendpoints = list;
}
/**
* @return The list of bendpoints in this relationship model.
*/
public List<GreenBendpoint> getBendpointList() {
return _bendpoints;
}
/**
* @return The name of the relationship.
*/
public String getRelationshipName() {
return PlugIn.getRelationshipName(getPartClass());
}
/**
* @return The <code>RelationshipGroup</code> that represents this kind of
* relationship
*/
public RelationshipGroup getRelationshipGroup() {
return PlugIn.getRelationshipGroup(getPartClass());
}
/**
* Hides/shows the relationship.
*
* @param isVisible - If true, shows the relationship; if false, hides it.
*/
public void setVisible(boolean isVisible) {
boolean modelVisible = isVisible;
if (!getRelationshipGroup().isVisible()) {
isVisible = false;
}
TypeModel sModel = getSourceModel();
TypeModel tModel = getTargetModel();
if (isVisible) {
// check to ensure that both source/target are visible
if (sModel == null || tModel == null) return;
if (!sModel.isVisible() || !tModel.isVisible()) return;
if (sModel.getImplicitRelationships().contains(this)) {
sModel.removeImplicitRelationship(this);
}
if (tModel.getImplicitRelationships().contains(this)) {
tModel.removeImplicitRelationship(this);
}
}
super.setVisible(modelVisible);
}
/**
* @see edu.buffalo.cse.green.editor.model.AbstractModel#toXML(edu.buffalo.cse.green.xml.XMLConverter)
*/
public void toXML(XMLConverter converter) {
converter.pushHeader(XML_RELATIONSHIP);
converter.writeKey(XML_RELATIONSHIP_CLASS, getPartClass().getName());
converter.writeKey(XML_RELATIONSHIP_SOURCE_TYPE,
_sourceType.getHandleIdentifier());
converter.writeKey(XML_RELATIONSHIP_TARGET_TYPE,
_targetType.getHandleIdentifier());
converter.pushHeader(XML_BENDPOINTS);
for (GreenBendpoint bendpoint : getBendpointList()) {
converter.pushHeader(XML_BENDPOINT);
converter.writeKey(XML_BENDPOINT_X,
bendpoint.getAbsoluteLocation().x);
converter.writeKey(XML_BENDPOINT_Y,
bendpoint.getAbsoluteLocation().y);
converter.popHeader();
}
converter.popHeader();
super.toXML(converter);
converter.popHeader();
}
/**
* Hides/shows the relationship and modifies the source and target type
* appropriately.
*
* @param show - If true, shows the relationship explicitly; if false, shows
* it implicitly.
*/
public void showRelationshipExplicitly(boolean show) {
if (isVisible() == show || !getRelationshipGroup().isVisible()) { return; }
if (show) { // show relationship explicitly
getSourceModel().removeImplicitRelationship(this);
getTargetModel().removeImplicitRelationship(this);
if (getSourceModel().isVisible() && getTargetModel().isVisible()) {
setVisible(true);
}
} else if (!show) { // show relationship implicitly
getSourceModel().addImplicitRelationship(this);
getTargetModel().addImplicitRelationship(this);
setVisible(false);
}
}
/**
* @see edu.buffalo.cse.green.editor.model.AbstractModel#getContextMenuFlag()
*/
public int getContextMenuFlag() {
return ContextAction.CM_RELATIONSHIP;
}
/**
* @see edu.buffalo.cse.green.editor.model.AbstractModel#getDeleteCommand(edu.buffalo.cse.green.editor.DiagramEditor)
*/
public DeleteCommand getDeleteCommand(DiagramEditor editor) {
return new DeleteRelationshipCommand(editor, this);
}
/**
* @param editor - The <code>DiagramEditor</code> containing this model.
*
* @return A command to hide this model.
*/
public Command getHideCommand(DiagramEditor editor) {
return new HideRelationshipCommand(this);
}
/**
* @see edu.buffalo.cse.green.editor.model.AbstractModel#getJavaElement()
*/
public IJavaElement getJavaElement() {
return null;
}
/**
* @return The set of relationships represented by this model.
*/
public Set<Relationship> getRelationships() {
return _relationships;
}
/**
* A command for deleting relationships.
*
* @author bcmartin
*/
class DeleteRelationshipCommand extends DeleteCommand {
private RelationshipModel _rModel;
private DiagramEditor _editor;
public DeleteRelationshipCommand(
DiagramEditor editor,
RelationshipModel relationship) {
_editor = editor;
_rModel = relationship;
}
/**
* @see edu.buffalo.cse.green.editor.model.commands.DeleteCommand#doDelete()
*/
public void doDelete() {
AbstractPart part = _editor.getRootPart().getPartFromModel(_rModel);
RelationshipGroup group = PlugIn.getRelationshipGroup(part.getClass());
RelationshipRemover remover = group.getRemover();
remover.setRelationship(_rModel);
remover.run(remover.getCompilationUnit(_rModel.getSourceType()), null);
remover.setRelationship(null);
getSourceModel().updateFields();
getSourceModel().updateMethods();
}
/**
* @see edu.buffalo.cse.green.editor.model.commands.DeleteCommand#getDeleteMessage()
*/
public String getDeleteMessage() {
if (getSourceType().isBinary()) {
return null;
}
return "Are you sure you want to delete that relationship?";
}
}
/**
* @param relationship - The relationship.
*
* @return An equivalent relationship if one is found inside this model,
* null otherwise.
*/
public Relationship contains(Relationship relationship) {
for (Relationship rel : _relationships) {
if (rel.equals(relationship)) {
return rel;
}
}
return null;
}
/**
* @see edu.buffalo.cse.green.editor.model.AbstractModel#removeFromParent()
*/
public void removeFromParent() {
RootModel root = getRootModel();
if (root.getChildren().contains(this)) {
if (getSourceModel() != null) {
getSourceModel().removeOutgoingEdge(this);
}
if (getTargetModel() != null) {
getTargetModel().removeIncomingEdge(this);
}
root.removeChildModel(this);
}
}
/**
* Updates the cardinality label of this model.
*/
public void updateCardinality() {
String cardinality;
try {
cardinality = getCardinality();
} catch (JavaModelException e) {
cardinality = "?";
}
firePropertyChange(RelationshipCardinality, null, cardinality);
}
/**
* @see edu.buffalo.cse.green.editor.model.AbstractModel#refresh()
*/
protected void refresh() {
updateCardinality();
setVisible(isVisible());
//
// super.refresh();
}
/**
* Sets the bounds of this model.
*
* @param bounds - The bounds to set.
*/
public void setBounds(Rectangle bounds) {
setLocation(bounds.getLocation());
setSize(bounds.getSize());
}
/**
* Ensures that the relationship being drawn is valid.
*/
public void assertValid() {
RelationshipGroup group = PlugIn.getRelationshipGroup(getPartClass());
try {
if (getSourceType().isClass()) {
if (getTargetType().isClass()) {
if (group.isValidClassToClass()) return;
} else if (getTargetType().isEnum()) {
if (group.isValidClassToEnum()) return;
} else if (getTargetType().isInterface()) {
if (group.isValidClassToInterface()) return;
}
} else if (getSourceType().isEnum()) {
if (getTargetType().isClass()) {
if (group.isValidEnumToClass()) return;
} else if (getTargetType().isEnum()) {
if (group.isValidEnumToEnum()) return;
} else if (getTargetType().isInterface()) {
if (group.isValidEnumToInterface()) return;
}
} else if (getSourceType().isInterface()) {
if (getTargetType().isClass()) {
if (group.isValidInterfaceToClass()) return;
} else if (getTargetType().isEnum()) {
if (group.isValidInterfaceToEnum()) return;
} else if (getTargetType().isInterface()) {
if (group.isValidInterfaceToInterface()) return;
}
}
GreenException.illegalOperation(REL_NOT_SUPPORTED);
} catch (JavaModelException e) {
GreenException.illegalOperation(e.getLocalizedMessage());
}
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return getSourceModel() + "," + getTargetModel() + "," + getPartClass();
}
/**
* @see edu.buffalo.cse.green.editor.model.AbstractModel#handleDispose()
*/
public void handleDispose() {
// do nothing
}
/**
* @see edu.buffalo.cse.green.editor.model.AbstractModel#createNewInstance(edu.buffalo.cse.green.editor.model.AbstractModel)
*/
public void createNewInstance(AbstractModel model) {
getRootModel().addChild((RelationshipModel) model);
}
}